#ifndef POLICY_H
#define POLICY_H

/**
 * @description: 判断inode是否是设备
 * @param {inode} *inode: 设备inode
 * @return {*} 0：不是设备；1：设备
 */
static __always_inline int inode_is_device(struct inode *inode)
{
    return S_ISBLK(inode->i_mode) || S_ISCHR(inode->i_mode);
}

/**
 * @description: 判断inode是否是网络动作
 * @param {inode} *inode :inode
 * @return {*} 0：不是；1：是
 */
static __always_inline int inode_is_network(struct inode *inode)
{
    return S_ISSOCK(inode->i_mode);
}

/**
 * @description: 判断inode是否为目录
 * @param {inode} *inode
 * @return {*}
 */
static __always_inline int inode_is_dir(struct inode *inode)
{
    return S_ISDIR(inode->i_mode);
}

/**
 * @description: 转化文件mask为access
 * @param {inode} *inode
 * @param {int} mask
 * @return {*}
 */
static __always_inline u32 mask_to_access(struct inode *inode, int mask)
{
    u32 access = 0x0;

    if (!inode)
        return 0;

    /* 单个文件 */
    // read
    if (mask & MAY_READ)
    {
        access |= FILE_MAY_READ;
    }

    // write & append
    if (mask & MAY_APPEND)
    {
        access |= FILE_MAY_APPEND;
    }
    else if (mask & MAY_WRITE)
    {
        access |= FILE_MAY_WRITE;
    }

    // exec，需要屏蔽掉当前inode是目录的情况
    if (!inode_is_dir(inode) && (mask && MAY_EXEC))
    {
        access |= FILE_MAY_EXEC;
    }

    /* 目录层级 */
    if (inode_is_dir(inode) && (mask & MAY_CHDIR))
    {
        access |= FILE_MAY_READ;
    }

    return access;
}

/**
 * @description: 转化当前file的f_mode为access进行统一接口处理
 * @param {inode} *inode
 * @return {*}
 */
static __always_inline u32 fmode_to_access(struct file *file)
{
    u32 access;

    u32 fmode = file->f_mode;

    // read
    if (fmode & FMODE_READ)
    {
        access |= FILE_MAY_READ;
    }

    // write
    if (fmode & FMODE_WRITE)
    {
        if (file->f_flags & O_APPEND)
        {
            access |= FILE_MAY_APPEND;
        }
        else
        {
            access |= FILE_MAY_WRITE;
        }
    }

    // exec
    if (fmode & FMODE_EXEC)
    {
        access |= FILE_MAY_EXEC;
    }

    return access;
}

/**
 * @description: 执行设备块相关的访控
 * @param {subject} *sub
 * @param {inode} *inode
 * @param {u64} access
 * @return {*}
 */
static __always_inline policy_decision_t do_dev_perm(subject_t *sub, struct inode *inode, u64 access)
{
    return LSM_NO_DECISION;
}

/**
 * @description: 执行进程task相关的访控
 * @param {subject} *sub
 * @param {inode} *inode
 * @param {u64} access
 * @return {*}
 */
static __always_inline policy_decision_t do_task_inode_perm(subject_t *sub, struct inode *inode, u64 access)
{
    return LSM_NO_DECISION;
}
/**
 * @description: 执行procfs相关的访控
 * @param {subject} *sub
 * @param {inode} *inode
 * @param {u64} access
 * @return {*}
 */
static __always_inline policy_decision_t do_procfs_perm(subject_t *sub, struct inode *inode, u64 access)
{
    return LSM_NO_DECISION;
}

/**
 * @description: 执行文件相关访控，从全局策略whole_file_policy中查看是否有相关权限，如果冲突以角色-文件访控为主
 * @param {subject} *sub
 * @param {inode} *inode
 * @param {u64} access
 * @return {*}
 */
static __always_inline policy_decision_t do_file_perm(subject_t *sub, struct inode *inode, u64 access)
{
    u32 decision = LSM_NO_DECISION;
    file_policy_key_t key = {};

    if (!sub)
        return LSM_NO_DECISION;

    // 获取主体角色id
    role_t *role = bpf_map_lookup_elem(&sub_to_role, sub);
    if (!role)
    {
        return LSM_NO_DECISION;
    }
    // 获取执行客体信息
    u64 inode_id = BPF_CORE_READ(inode, i_ino);
    // 填充策略键
    key.role_id = role->id;
    key.inode_id = inode_id;

    // 获取全局中该文件对应的权限
    file_policy_val_t *whole_val = bpf_map_lookup_elem(&whole_file_policy, &inode_id);
    // 获取角色与文件对应权限
    file_policy_val_t *val = bpf_map_lookup_elem(&file_policy, &key);
    if (val)
    {
        goto role_decision;
    }

    // 全局对这次文件访控进行初次判定
    if (whole_val && (whole_val->allow & access) == access)
    { // 所有权限均匹配才准许通过
        decision |= LSM_ALLOW;
    }
    if (whole_val && (whole_val->deny & access))
    { // 只要有deny，就不准许通过
        decision |= LSM_DENY;
    }

role_decision:
    // 做出这次文件访控的决定
    if (val && (val->allow & access) == access)
    { // 所有权限都匹配才可以通过
        decision |= LSM_ALLOW;
    }
    if (val && (val->deny & access))
    { // 只要有deny，那么就不可以通过
        decision |= LSM_DENY;
    }
    return decision;
}

/**
 * @description: 执行fs相关访控，从全局策略whole_fs_policy中查看是否有相关权限，如果冲突以角色-文件访控为主
 * @param {subject} *sub
 * @param {inode} *inode
 * @param {u64} access
 * @return {policy_decision_t} decision 本次访问结果
 */
static __always_inline policy_decision_t do_fs_perm(subject_t *sub, struct inode *inode, u64 access)
{
    u32 decision = LSM_NO_DECISION;
    fs_policy_key_t key = {};

    if (!sub)
        return LSM_NO_DECISION;

    // 获取主体角色id
    role_t *role = bpf_map_lookup_elem(&sub_to_role, sub);
    if (!role)
    {
        return LSM_NO_DECISION;
    }
    // 获取执行客体信息
    u32 fs_dev = BPF_CORE_READ(inode, i_sb, s_dev);

    // 填充策略键
    key.role_id = role->id;
    key.fs_dev = fs_dev;

    // 获取全局中该文件对应的权限
    file_policy_val_t *whole_val = bpf_map_lookup_elem(&whole_fs_policy, &fs_dev);
    // 获取角色与文件对应权限
    file_policy_val_t *val = bpf_map_lookup_elem(&fs_policy, &key);
    if (val)
    {
        goto role_decision;
    }

    // 全局对这次文件访控进行初次判定
    if (whole_val && (whole_val->allow & access) == access)
    { // 所有权限均匹配才准许通过
        decision |= LSM_ALLOW;
    }
    if (whole_val && (whole_val->deny & access))
    { // 只要有deny，就不准许通过
        decision |= LSM_DENY;
    }

role_decision:
    // 做出这次文件访控的决定
    if (val && (val->allow & access) == access)
    {                            // 所有权限都匹配才可以通过
        decision &= ~(LSM_DENY); // 清空全局策略影响
        decision |= LSM_ALLOW;
    }
    if (val && (val->deny & access))
    { // 只要有deny，那么就不可以通过
        decision |= LSM_DENY;
    }
    return decision;
}

/**
 * @description: 最终访控返回给lsm的结果
 * @param {policy_decision_t} decision 决定
 * @return {*} 给lsm的结果
 */
static __always_inline int do_policy_decision(policy_decision_t decision)
{
    if (decision & LSM_DENY)
    {
        return -EACCES;
    }
    if (decision & LSM_ALLOW)
    {
        return 0;
    }
    if (decision & LSM_NO_DECISION)
    {
        return 0;
    }
    return 0;
}

/**
 * @description: 用于决定inode所指文件是否应被access标记访问
 * @param {struct inode*} inode:文件的inode
 * @param {u32} access: 访问许可标记
 * @return {int} ret: 访问结果标记
 */
static __always_inline int
lsm_inode_perm(subject_t *sub, struct inode *inode, u64 access)
{
    int ret = 0;
    policy_decision_t decision = LSM_NO_DECISION;

    /* 判断该inode属于文件、网络还是设备块 */
    if (inode_is_device(inode))
    { // 设备块
        decision |= do_dev_perm(sub, inode, access);
        ret = do_policy_decision(decision);
        goto audit;
    }

    if (inode_is_network(inode))
    { // 网络
        // decision |= do_net_perm(sub, inode, access);
        // ret = do_policy_decision(decision);
        goto audit;
    }

    /* File acl */
    // 如果允许文件访问，那么应该首先清楚系统中文件相关的访问控制中对其的拒绝
    decision |= do_task_inode_perm(sub, inode, access);
    decision |= do_procfs_perm(sub, inode, access);
    decision |= do_file_perm(sub, inode, access);

    /* File system acl */
    decision |= do_fs_perm(sub, inode, access);

    ret = do_policy_decision(decision);

audit:
    if (ret == 0)
    {
        return ret;
    }
    if (inode_is_network(inode))
    {

        return ret;
    }
    else
    { // 文件相关（单个文件、文件目录）
        struct audit_event *e = alloc_audit_event(0, AUDIT_TYPE_FILE);
        if (e)
        {
            e->file_audit.st_ino = inode->i_ino;
            e->file_audit.st_dev = BPF_CORE_READ(inode, i_sb, s_dev);
            e->file_audit.access = access;
            e->decision = decision;
            submit_audit_event(e);
            return ret;
        }
    }
    return ret;
}

static __always_inline policy_decision_t
do_net_perm(subject_t *sub, struct sockaddr_in *addr, u32 access)
{

    policy_decision_t decision = LSM_NO_DECISION;
    net_policy_key_t key = {0x00000000, 0x00000000};

    if (!sub)
    {
        return LSM_NO_DECISION;
    }

    // 获取主体角色id
    role_t *role = bpf_map_lookup_elem(&sub_to_role, sub);
    if (!role)
    {
        return LSM_NO_DECISION;
    }
    // 获取执行客体信息
    u32 ip = BPF_CORE_READ(addr, sin_addr.s_addr);
    // 填充策略键
    key.role_id = role->id;
    key.ip = ip;

    // 获取全局中该文件对应的权限
    net_policy_val_t *whole_val = bpf_map_lookup_elem(&whole_net_policy, &ip);
    // 获取角色与文件对应权限
    net_policy_val_t *val = bpf_map_lookup_elem(&net_policy, &key);

    if (val)
    {
        goto role_decision;
    }

    // 全局对这次文件访控进行初次判定
    if (whole_val && (whole_val->allow & access) == access)
    { // 所有权限均匹配才准许通过
        decision |= LSM_ALLOW;
    }
    if (whole_val && (whole_val->deny & access))
    { // 只要有deny，就不准许通过
        decision |= LSM_DENY;
    }

role_decision:
    // 做出这次文件访控的决定
    if (val && (val->allow & access) == access)
    { // 所有权限都匹配才可以通过
        decision |= LSM_ALLOW;
    }
    if (val && (val->deny & access))
    { // 只要有deny，那么就不可以通过
        decision |= LSM_DENY;
    }

    return decision;
}

/**
 * @description: 用于决定addr结构中的客体 否应被access标记访问
 * @param {struct sockaddr_in*} inode:提取的客体信息
 * @param {u32} access: 访问许可标记
 * @return {int} ret: 访问结果标记
 */
static __always_inline int lsm_net_perm(subject_t *sub, struct sockaddr_in *addr, u32 access)
{
    int ret = 0;
    policy_decision_t decision = LSM_NO_DECISION;

    decision |= do_net_perm(sub, addr, access);
    ret = do_policy_decision(decision);

    struct audit_event *e = alloc_audit_event(0, AUDIT_TYPE_NET);
    if (e)
    {
        e->net_audit.uid = (u32)bpf_get_current_uid_gid();
        e->net_audit.ip = BPF_CORE_READ(addr, sin_addr.s_addr);
        e->net_audit.opt = access;
        e->decision = decision;
        submit_audit_event(e);
        return ret;
    }

    return ret;
}

#endif // !POLICY_H
